#include "code_convert.hpp"
#include "clang_utf.hpp"
#include "gb_table_search.hpp"
#include "gb_conv_internal.hpp"

#include <cassert>
#include <locale>
#include <vector>

namespace jjwf {
namespace codecvt {
namespace detail {

/**
  * @breif 提供基于系统的 区域多字节multi byte 与 wchar 之间的转换
  *   是否成功取决于：1. 系统区域语言包  2. 区域名是否设置正确
  */
#if defined(_WIN32)
struct StdCLocaleConverter
{
	StdCLocaleConverter(const char* loc_name)
	{
		loc_ = ::_create_locale(LC_CTYPE, loc_name);
	}
	~StdCLocaleConverter()
	{
		if (loc_) ::_free_locale(loc_);
	}
	bool loc_is_null()
	{
		return loc_ == NULL;
	}
	size_t mbstowcs(wchar_t* dest, const char* src, size_t max)
	{
		return ::_mbstowcs_l(dest, src, max, loc_);
	}
	size_t wcstombs(char* dest,  const wchar_t* src, size_t max)
	{
		return ::_wcstombs_l(dest, src, max, loc_);
	}
private:
	_locale_t loc_;
};
#elif defined(__APPLE__)
struct StdCLocaleConverter
{
	StdCLocaleConverter(const char* loc_name)
	{
		loc_ = ::newlocale(LC_CTYPE_MASK, loc_name, (locale_t)0);

		if (loc_ != (locale_t)0) ::uselocale(loc_);
	}
	~StdCLocaleConverter()
	{
		if (loc_ != (locale_t)0) ::freelocale(loc_);
	}
	bool loc_is_null()
	{
		return loc_ == (locale_t)0;
	}
	size_t mbstowcs(wchar_t* dest, const char* src, size_t max)
	{
		return ::mbstowcs_l(dest, src, max, loc_);
	}
	size_t wcstombs(char* dest,  const wchar_t* src, size_t max)
	{
		return ::wcstombs_l(dest, src, max, loc_);
	}
private:
	locale_t loc_;
};
#else
// 迄今linux 下 ansi c 没有收录 posix c的 mbstowcs_l wcstombs_l 的函数
struct StdCLocaleConverter
{
	StdCLocaleConverter(const char* loc_name)
	{
		::setlocale(LC_CTYPE, loc_name);
	}
	bool loc_is_null()
	{
		return false;
	}
	size_t mbstowcs(wchar_t* dest, const char* src, size_t max)
	{
		return ::mbstowcs(dest, src, max);
	}
	size_t wcstombs(char* dest,  const wchar_t* src, size_t max)
	{
		return ::wcstombs(dest, src, max);
	}
};
#endif
} // end of namespace detail

std::wstring ansi_to_wstr(const std::string& ansi, const char* loc_name)
{
	std::wstring wcs_out;
	detail::StdCLocaleConverter conv(loc_name);

	if (!conv.loc_is_null())
	{
		size_t wcs_capacity = conv.mbstowcs(NULL, ansi.c_str(), 0) + 2;
		wcs_out.resize(wcs_capacity);

		size_t wcs_size = conv.mbstowcs((wchar_t*)wcs_out.c_str(),
										ansi.c_str(), wcs_capacity);

		wcs_out.resize(
			wcs_size == size_t(-1) ? 0 : wcs_size
		);
	}

	return std::move(wcs_out);
}

std::string  wstr_to_ansi(const std::wstring& wstr, const char* loc_name)
{
	std::string mbs_out;
	detail::StdCLocaleConverter conv(loc_name);

	if (!conv.loc_is_null())
	{
		size_t mbs_capacity = conv.wcstombs(NULL, wstr.c_str(), 0) + 2;
		mbs_out.resize(mbs_capacity);

		size_t mbs_size = conv.wcstombs((char*)mbs_out.c_str(),
										wstr.c_str(), mbs_capacity);

		mbs_out.resize(
			mbs_size == size_t(-1) ? 0 : mbs_size
		);
	}

	return std::move(mbs_out);
}

std::wstring utf8_to_wstr(const std::string& utf8)
{
	std::wstring out;
	out.resize(utf8.size() + 1, L'\0');

	const UTF8* in_begin = (const UTF8*)utf8.c_str();
	const UTF8* in_end = (const UTF8*)utf8.c_str() + utf8.size();

#if defined(_WIN32)
	assert(sizeof(wchar_t) == 2);

	UTF16* out_begin = (UTF16*)out.c_str();
	UTF16* out_end = (UTF16*)out.c_str() + out.size();

	ConvertUTF8toUTF16(&in_begin, in_end,
					   &out_begin, out_end, strictConversion);

	out.resize(out_begin - (UTF16*)out.c_str());
#else
	assert(sizeof(wchar_t) == 4);

	UTF32* out_begin = (UTF32*)out.c_str();
	UTF32* out_end = (UTF32*)out.c_str() + out.size();

	ConvertUTF8toUTF32(&in_begin, in_end,
					   &out_begin, out_end, strictConversion);

	out.resize(out_begin - (UTF32*)out.c_str());
#endif
	return std::move(out);
}

std::string  wstr_to_utf8(const std::wstring& wstr)
{
	std::string out;
	out.resize(wstr.size() * 4 + 1, L'0');

	UTF8* out_begin = (UTF8*)out.c_str();
	UTF8* out_end = (UTF8*)out.c_str() + out.size();

#if defined(_WIN32)
	assert(sizeof(wchar_t) == 2);

	const UTF16* in_begin = (const UTF16*)wstr.c_str();
	const UTF16* in_end = (const UTF16*)wstr.c_str() + wstr.size();

	ConvertUTF16toUTF8(&in_begin, in_end,
					   &out_begin, out_end, strictConversion);

	out.resize(out_begin - (UTF8*)out.c_str());
#else
	assert(sizeof(wchar_t) == 4);

	const UTF32* in_begin = (const UTF32*)wstr.c_str();
	const UTF32* in_end = (const UTF32*)wstr.c_str() + wstr.size();

	ConvertUTF32toUTF8(&in_begin, in_end,
					   &out_begin, out_end, strictConversion);

	out.resize(out_begin - (UTF8*)out.c_str());
#endif

	return std::move(out);
}

std::string utf8_to_ansi(const std::string& utf8, const char* locale)
{
	std::wstring wstr = std::move(utf8_to_wstr(utf8));
	std::string  ansi = std::move(wstr_to_ansi(wstr, locale));
	return std::move(ansi);
}

std::string ansi_to_utf8(const std::string& ansi, const char* locale)
{
	std::wstring wstr = std::move(ansi_to_wstr(ansi, locale));
	std::string utf8 = std::move(wstr_to_utf8(wstr));
	return std::move(utf8);
}

} // end of namespace codecvt
} // end of namespace jjwf

namespace jjwf {
namespace codecvt {

std::string utf8_to_gb18030(const std::string& utf8)
{
	std::wstring wstr = std::move(utf8_to_wstr(utf8));
	std::string  strgb = std::move(wstr_to_gb18030(wstr));
	return std::move(strgb);
}

std::string gb18030_to_utf8(const std::string& strgb)
{
	std::wstring wstr = std::move(gb18030_to_wstr(strgb));
	std::string  strutf8 = std::move(wstr_to_utf8(wstr));
	return std::move(strutf8);
}

std::wstring gb18030_to_wstr(const std::string& strgb)
{
	return _gb_to_wstr(strgb);
}

std::string wstr_to_gb18030(const std::wstring& wstr)
{
	return std::move(_wstr_to_gb(wstr));
}

} // end of namespace codecvt

} // end of namespace jjwf
